#!/usr/bin/env python3
# from __env import __import

# __import(__file__, '''
# from my_dbg import *
from List import List
from Dir_tree import *

# ''')
# from auto_import import *


import ast
from Node_trans import Node_trans, C_maker
from Type_space import Type_space, H_maker
from pyc_to_python import Pyc_to_python
#from pythonic_c_release.make_db import Mysql_node_trans
from py_stack_parser import Pyc_parser
from Node_trans_cfg import Name_switch

class Tree_module:
    @staticmethod
    def get_class_list(module_node): 
        return [node for node in module_node.body if node.__class__.__name__ == "ClassDef"]
    @staticmethod
    def get_func_list(class_node): 
        return [node for node in class_node.body if node.__class__.__name__ == "FunctionDef"]
    @staticmethod
    def func_code(func_node): return func_node.body
    
    def __init__(self, src_line):#f_name, bak_f_name=None):
        self.module = None
        self.code = None
        self.init(src_line)#f_name, bak_f_name)
    def get_func_node(self, class_name, func_name):
        class_node = List.find_e(self.class_list(), lambda e: e.name == class_name)
        if class_name == None: return None
        func_node = List.find_e(Tree_module.func_list(class_node), lambda e: e.name == func_name)
        return func_node

    def init(self, src_line):#f_name, bak_f_name=None):
        self.code = "\n".join([ Pyc_to_python.line_filter(line) for line in src_line])
        code = self.code
        try:
            tree = ast.parse(code)
            self.module = tree
        except Exception as e:
            OO_info("parse Exception")
            for i, line in enumerate(code.split("\n")):
                OO_info("[{i}\t] {line}".format(**locals()))
            raise e
    def class_list(self):
        return Tree_module.get_class_list(self.module)
    def func_list(self, class_name):
        class_node = List.find_e(self.class_list(), lambda class_node: class_node.name == class_name)
        return Tree_module.get_func_list(class_node)


class Compile:
    def __init__(self, pyc_dir, inc_common_dir, inc_dir, src_dir, py_dir_bak=None, single=False, template_dict=None):
        self.dir = Dir_tree()
        self.pyc_dir = self.dir/ pyc_dir
        self.py_dir_bak = self.dir/ py_dir_bak if py_dir_bak != None else None
        print("py_dir_bak ==None : {}".format(self.py_dir_bak==None))
        self.inc_common_dir = self.dir/ inc_common_dir
        self.inc_dir = self.dir/ inc_dir
        self.src_dir = self.dir/ src_dir
        #self.template_dir = self.dir/template_dir
        self.template_dict = template_dict

        self.type_space = Type_space()
        self.single= single

        self.translate_map = {}  # class_name : {"pyc":xxx, "py":xxx, "c":xxx}
        self.module_relys = []
    
    def add_template_pass(self, class_name):
        class_deco_list = self.translate_map[class_name]["deco_list"]
        template_list = []
        for deco in class_deco_list:
            pre_template = "@Template_"
            if deco.startswith(pre_template):
                print("<{deco}>  {class_name}".format(**locals()))
                template_name = deco.split(pre_template, 1)[1]
                template_list.append(template_name)
        for template_name in template_list:
            build_pass = """@build( self.template_dict["{template_name}"].build_pass(self, class_name) )""".format(**locals())
            create_module_pass = """@create_module( self.template_dict["{template_name}"].create_module_pass(self, class_name) )""".format(**locals())
            class_deco_list.append(build_pass)
            class_deco_list.append(create_module_pass)
            

    def get_deco_pass(self, class_name, pass_name):
        class_deco_list = self.translate_map[class_name]["deco_list"]
        r_list = []
        pre_pass_code, after_pass_code = "@" + pass_name+"(", ")"
        for deco in class_deco_list:
            # @pass_name( python_code )
            # eg: @build( Template_mysqldb.add_class() )
            if deco.startswith(pre_pass_code):
                python_code = deco.split(pre_pass_code, 1)[1].rsplit(after_pass_code, 1)[0].strip()
                r_list.append(python_code)
        return r_list
    def pass_todo(self, class_name, pass_name):
        #for class_name in self.translate_map:
        todo_list = self.get_deco_pass(class_name, pass_name)
        #print("todo_list {todo_list}".format(**locals()))
        for todo in todo_list:
            class_name = class_name
            class_info = self.translate_map[class_name]
            eval(todo)

    def try_init_class_info(self, class_name):
        if class_name not in self.translate_map:
            self.translate_map[class_name] = {}
            d = self.translate_map[class_name]
            d["pyc_list"] = []
            d["py"] = []
            d["c"] = []
            d["rely"] = []
            d["module"] = None
            
            d["deco_list"] = []
            d["opt_list"] = []
            d["code_list"] = []
            d["define_line"] = ""
            d["init_member"] = []
            d["__under"] = None
    
    def generate_file_name(self):
        def get_deco_func(deco_list, func_name):
            for deco in deco_list:
                if deco.startswith("@"+func_name):
                    return deco
            return None
        change = False
        while 1:
            for class_name in self.translate_map:
                d = self.translate_map[class_name]
                if d["__under"] == None:
                    func_name = "__under"
                    deco_under = get_deco_func(d["deco_list"], func_name)
                    if deco_under != None:
                        under_target = deco_under.split("(", 1)[1].rsplit(")", 1)[0].strip()
                        d["__under"] = under_target
                        change = True;break
            if change == False: break
            change = False
    def get_file_name(self, class_name):
        cur_name = class_name
        while 1:
            father_name = self.translate_map[cur_name]["__under"] 
            if father_name == None:
                return cur_name
            else:
                cur_name = father_name
                    
    def assumble_class(self, class_name):
        d = self.translate_map[class_name]
        d["pyc_list"] = d["deco_list"] + [ d["define_line"] ] + d["opt_list"] + d["init_member"] + d["code_list"]
        #############5.27
        for opt_line in d["opt_list"]:
            if opt_line.startswith("    rely"):
                relys = opt_line.split("=")[1].strip()[1:-1].split(" ")
                d["rely"] += list(filter(None, relys))
        

        # #############debug##################
        if not "#######debug":
            log_list = []
            for i,line in enumerate( d["pyc_list"] ):
                log_list.append("[{i}] {line}".format(**locals()))
            log_file2 = Dir_tree()/"log_info2"
            log_file2 + "====opt_list===="
            log_file2 + d["opt_list"]

        # log_file2 + log_list


    def show_class(self, class_name):
        d = self.translate_map[class_name]
        pyc_list, deco_list, opt_list, code_list, define_line = "\n".join(d["pyc_list"]) , "\n".join(d["deco_list"]) , "\n".join(d["opt_list"]) , "\n".join(d["code_list"]), d["define_line"]
        OO_info("{define_line}\npyc_list[{pyc_list}]\ndeco_list[{deco_list}]\nopt_list[{opt_list}]\ncode_list[{code_list}]".format(**locals()))
    # def load_template(self, template_name):
    #     template_file = Dir_tree()/"Template_cur.py"
    #     if not template_file.exists():
    #         template_file << ""
    #     template_file + str(self.pyc_dir/template_name)
    #     import Template_cur
    #     template = "Template_cur.{template_name}()".format(**locals())
    #     return template
    # def add_template(self, src_class_name, deco_list):
    #     for deco in deco_list:
    #         if deco startswith("Template_make"):#  @Template_make(template_name)
    #             template_name = deco.split("(")[1].split(")")[0].strip()
    #             template_name += ".py"
    #             template = self.load_template(template_name)
    #             target_class_name = template.target_class_name(src_class_name, self.translate_map)



    #def build_relys(self, line_list, file_name):###########5.27
    def build_relys(self, line_list):
        
        def func_node_to_list(func_node):
            return func_node.deco_list + [func_node.line] + func_node.code_list
        def parser_to_translate_map(parser, translate_map):
            for class_node in parser.project.values():
                class_name = class_node.name
                if class_name not in self.translate_map:
                    self.try_init_class_info(class_name)
                translate_map[class_name]["deco_list"] += class_node.deco_list
                translate_map[class_name]["opt_list"] += class_node.opt_list
                translate_map[class_name]["define_line"] = class_node.line
                translate_map[class_name]["init_member"] += func_node_to_list(class_node.func_dict["__init__"]) if "__init__" in class_node.func_dict else []
                for func_node in class_node.func_dict.values():
                    if func_node.name == "__init__": continue
                    #func_list = func_node.deco_list + [func_node.line] + func_node.code_list
                    translate_map[class_name]["code_list"] += func_node_to_list(func_node)
                ###########5.27  move rely here
                # for opt_line in d["opt_list"]:
                #     if opt_line.startswith("    rely"):
                #         relys = opt_line.split("=")[1].strip()[1:-1].split(" ")
                #         d["rely"] += list(filter(None, relys))
                
        parser = Pyc_parser()
        parser.from_pyc_line_list(line_list)
        parser_to_translate_map(parser, self.translate_map)
        


    def build_ext(self):
        class_name_list = list(self.translate_map.keys()) [::-1][::-1]
        for class_name in class_name_list:
            self.add_template_pass(class_name)

            self.pass_todo(class_name, "build")

    def create_relys_module(self, relys):
        def create_module(Class, pyc_list):
            
            if self.py_dir_bak != None:
                py_file = Class+".py"
                print("bak:: {py_file}".format(**locals()))
                self.py_dir_bak/py_file  << pyc_list


            module = Tree_module(pyc_list)
            self.translate_map[Class]["module"] = module
            self.translate_map[Class]["py"] = module.code
            return module
        for Class in relys:
            self.pass_todo(Class, "create_module")
            pyc_list = self.translate_map[Class]["pyc_list"]
            if not "print_pyc":
                pyc_list_dbg = "\n".join(pyc_list)
                OO_info("{Class} pyc_list:\n{pyc_list_dbg}".format(**locals()))
            
            module = create_module(Class, pyc_list)
    def load_pyc(self):
        pyc_file_dict = {}
        for d in self.pyc_dir.glob("*"):
            if not d.is_dir():
                pyc_file_dict[d.path()] = list(d)
        for file_name in pyc_file_dict:
            print(file_name)
            #self.build_relys(pyc_file_dict[file_name], file_name)############5.27
            self.build_relys(pyc_file_dict[file_name])
        print("file-name finish!")
        self.build_ext()
        # one class may build multi time.
        # so assumble it later.
        for class_name in self.translate_map:
            print(class_name)
            self.assumble_class(class_name)
        print("load_pyc finish!")


    def get_class_from_line(self, line):################################
        r = line.split("class ")[1].split(":")[0].strip()
        if "(" in r: r = r.split("(", 1)[0].strip()
        return r


    def record_file_list_to_file(self, f_list, f_tree):
        # 跟进类 只能依赖头部类。
        # 头部类要把所有跟进类的  依赖都提前包含进来。

        # 这个约定可以
        # 1 自动让头文件置顶， 
        # 2 对软件组织增加了更强的限制。
        # 3 在计算文件依赖关系的时候， 非常好计算。
        code_list = []
        for file_name in f_list:
            #头部类
            class_name = file_name
            rely_list = self.find_all_class_rely(class_name)
            print("<{class_name}> {rely_list}".format(**locals()))
            
            file_list = self.rely_list_to_file_list(rely_list)
            code_list.append(  "<{class_name}> {file_list}".format(**locals()) )
        
        
        f_tree << code_list
        


    def find_all_class_rely(self, class_name):
        rely_list = [class_name]
        
        changed = True
        while changed:
            changed = False
            for class_name in rely_list:
                # mysql_source_class = self.mysql_auto_file_source(class_name)
                # if mysql_source_class != None:
                #     self.make_mysql_auto_file(mysql_source_class)
                print(class_name)
                print( list(self.translate_map.keys()) )
                ext_relys = self.translate_map[class_name]["rely"]
                
                for ext_rely in ext_relys:
                    if ext_rely not in rely_list:
                        rely_list.append(ext_rely)
                        changed = True
        ordered_rely_list = []
        while len(ordered_rely_list) < len(rely_list):
            for class_name in rely_list:
                if class_name in ordered_rely_list:
                    continue
                all_rely_inside = True
                for sub_class in self.translate_map[class_name]["rely"]:
                    if sub_class not in ordered_rely_list:
                        all_rely_inside = False
                if all_rely_inside == True:
                    ordered_rely_list.append(class_name)
        return ordered_rely_list

    def compile_class_h(self, class_name):
        module = self.translate_map[class_name]["module"]
        for class_node in module.class_list():
            if class_node.name == class_name:
                f_name = self.h_maker(class_node)
                return f_name
                #self.translate_map[class_name]["py"] = module.code
    def compile_class_c(self, class_name):
        module = self.translate_map[class_name]["module"]
        for class_node in module.class_list():
            if class_node.name == class_name:
                self.c_maker(module, class_node)

    def file_gap(self, class_name, h_or_c):
        mid_str = "//"+"."*38
        return mid_str + "{class_name}.{h_or_c}".format(**locals()) + mid_str


    def rely_list_to_file_list(self, rely_list):
        f_list = []
        for rely in rely_list:
            f_name = self.get_file_name(rely)
            if f_name not in f_list:
                f_list.append(f_name)
        return f_list

    def add_header_guard(self, file_name):
        code_file = self.inc_dir/(file_name+".h")
        code = str(code_file)
        code_file << """
#ifndef __H_module_{file_name}_H__
#define __H_module_{file_name}_H__

{code}

#endif //__H_module_{file_name}_H__
""".format(**locals())

    def h_maker(self, class_node):#and namespace
        file_name = ""
        h_text = self.type_space.trans(class_node)
        relys = self.translate_map[class_node.name]["rely"]
        module_header = "\n".join([ "#include <{class_name}.h>".format(**locals()) for class_name in relys ])
        if self.single == True:
            file_name = "single"
            #(self.inc_dir/ "single.h" ) + "/*\n" + module_header + "*/\n"
            self.inc_dir/ "single.h" + h_text
            #self.inc_dir/ "single.h" + self.file_gap(class_node.name, "h")
            
        else:
            file_name = self.get_file_name(class_node.name)
            f_list = self.rely_list_to_file_list(relys)

            # remove file_name from #include <>
            module_header = "\n".join([ "#include <{cur_f_name}.h>".format(**locals()) for cur_f_name in f_list if cur_f_name != file_name])
            h_file = file_name + ".h"
            (self.inc_dir/ h_file ) + module_header + "\n" + h_text
        return file_name
    def c_maker(self, module, class_node):
        c_file = C_maker(module, class_node, self.type_space)
        try:
            func_code = c_file.func_code()#dir=self.src_dir)
        except Exception as e:
            OO_info("\n\n--------------------------------------------")
            OO_info(self.translate_map[class_node.name]["py"])
            OO_info("\n--------------------------------------------")
            raise e

        if self.single == True:
            self.src_dir/ "single.c" + func_code
            self.src_dir/ "single.c" + self.file_gap(class_node.name, "c")
        else:
            
            class_name = class_node.name
            file_name = self.get_file_name(class_name)
            c_text = func_code

            if file_name == class_name:
                c_text = """
#include <{file_name}.h>

""".format(**locals()) + c_text
            c_file = file_name + ".c"
            (self.src_dir/ c_file ) + c_text
    def clear_dir(self, Dir):
        Dir.rm()
        Dir.mkdir()
    def make_class_and_ext(self, class_name):
        if self.py_dir_bak != None:
            self.clear_dir(self.py_dir_bak)
        self.clear_dir(self.inc_dir)
        self.clear_dir(self.src_dir)

        self.load_pyc()
        relys = self.find_all_class_rely(class_name)
        print("relys")
        print(relys)
        self.create_relys_module(relys)
        
        #ext_command = ""
        if self.single == True:
            self.inc_dir/"single.h" << ""
            self.src_dir/"single.c" << "#include <single.h>"

        self.generate_file_name()
        
        header_list = []
        for class_name in relys:
            header_name = self.compile_class_h(class_name)
            if header_name not in header_list:
                header_list.append(header_name)
        for class_name in relys:
            self.compile_class_c(class_name)
        
        # add header_guard
        for header_name in header_list:
            self.add_header_guard(header_name)

        self.module_relys = relys#, ext_command
    
    #@staticmethod
    def shell(self, command, *params):
        import subprocess
        o = command.split(" ")
        o = list(filter(None, o))
        for p in params:
            o.append(p)
        r = subprocess.Popen(o ,stdout=subprocess.PIPE).communicate()[0]
        r = str(r, encoding="utf-8")
        r = r.splitlines()
        r = list( filter(None, r) )
        return r
    
    
    def main_name(self, test_name):   return "main_{test_name}.c".format(**locals())
    def create_main(self, module_name):
        unit_test_list = []
        for func_info in self.type_space.class_space[module_name]["func"]:
            func_name = func_info.name
            # typespace 里面的func name 有重复。 这应该和编译过程有关。 
            if func_name.startswith("unit_test"):
                test_name = func_name[len("unit_test"):]
                if test_name not in unit_test_list:
                    unit_test_list.append(test_name)
        
        
        header_name = module_name
        if self.single == True:
            header_name = "single"
        else:
            header_name = self.get_file_name(header_name)
        for test_name in unit_test_list:
            ##!! 单元测试函数调用
            unit_test_name = "{module_name}__unit_test{test_name}".format(**locals())
            unit_test_name = Name_switch.Camel(unit_test_name)
            
            exception_clear = Name_switch.Camel("Ds_exception__print_and_clear_error")
            self.src_dir / self.main_name(test_name) << """
#include <{header_name}.h>


int main()
{{
    void* DS_ERROR_INFO = NULL;
    //{module_name}__unit_test{test_name}(&DS_ERROR_INFO);
    {unit_test_name}(&DS_ERROR_INFO);
    if (DS_ERROR_INFO != NULL)
    {{
        //Ds_exception__print_and_clear_error(&DS_ERROR_INFO);
        {exception_clear}(&DS_ERROR_INFO);
    }}
}}
            """.format(**locals())

        return unit_test_list
    def make_bin(self, ext_command=""):
        relys = self.module_relys
        # if self.py_dir_bak != None:
        #     for class_name in relys:
        #         if class_name in self.translate_map:
        #             py_file = class_name+".py"
        #             print("bak:: {py_file}".format(**locals()))
        #             self.py_dir_bak/py_file  << self.translate_map[class_name]["pyc_list"]
        
        unit_test_list = self.create_main(relys[-1])
        inc = self.inc_dir.path()
        src = self.src_dir.path()
        inc_common = self.inc_common_dir.path()
        file_list = self.rely_list_to_file_list(relys)
        


        # rely_list_not_same = []
        # for rely in relys:
        #     file_name = self.get_file_name(rely)
        #     if file_name not in rely_list_not_same:
        #         rely_list_not_same.append(file_name)
        #rely_list = [ "{src}/{class_name}.c".format(class_name=self.get_file_name(class_name), src=src) for class_name in relys]
        
        relys_str = " ".join([ "{src}/{file_name}.c".format(src=src, file_name=file_name) for file_name in file_list])

        

        if self.single == True:
            relys_str = "{src}/single.c".format(**locals())
        else:
            self.record_file_list_to_file(file_list, Dir_tree()/"c_code"/"module_list.rely")
        
        import os
        os.system("rm *_bin")
        print("====   GCC COMPILER   ====")

        for test_name in unit_test_list:
            main_name = self.main_name(test_name)
            
            #################
            gcc = "gcc {src}/{main_name} {relys_str} {inc_common}/rdps_log.c {inc_common}/buffer.c -I{inc} -I{inc_common} {ext_command}  -o {test_name}_bin -O0".format(**locals())
            OO_info("\n\n\n")
            

            self.shell(gcc)

            OO_info("\n\n"+gcc+"\n")

def pyc_compiler__go(template_dict):
    
    import sys
    this_file = sys.argv[0]
    if len(sys.argv) < 2:
        OO_info("""usage: ./{this_file} [module_name] [opt]\n

    opt:   
        gdb=True
        bak=True
        \n""".format(**locals()))
    
    else:
        module_name = sys.argv[1]
        opt = {
            'bak' : False,
            'gdb' : False,
            "single": True,
        }
        if len(sys.argv) > 2:
            opt_list = sys.argv[2:]
            for opt_pair in opt_list:
                key, value = opt_pair.strip().split("=")
                opt[key] = eval(value)
                OO_info("opt[{key}] = {value}".format(**locals()))
        ext_command = "-I/usr/include/mysql -lmysqlclient -lpthread -lm"
        
        #single = False

        if opt['gdb'] == True:
            ext_command += " -g"
        py_dir_bak = "0_py_c/L_chd_bak" 
        if opt['bak'] != True:
            py_dir_bak = None

        OO_info(opt)
        
        c = Compile(pyc_dir="0_py_c/L_chd", inc_common_dir="1_inc_common", inc_dir="2_inc", src_dir="3_src", py_dir_bak=py_dir_bak, single=opt["single"], template_dict=template_dict)
        c.make_class_and_ext(module_name)
        #ext_command = ext_command
        c.make_bin(ext_command)

if __name__ == "__main__":
    pyc_compiler__go()
